//: -version=DEV1 -d

import
	Utf	= tango.text.convert.Utf ,
	Int	= tango.text.convert.Integer ,
	Txt	= tango.text.Util ,
	tango.text.json.Json ,
	
	tango.sys.Environment ,
	tango.sys.Process ,
	tango.sys.Common ,
	tango.sys.win32.UserGdi ,
	tango.sys.win32.SpecialPath ,
	
	tango.io.FilePath,
	tango.io.FileScan,
	Pth	= tango.io.Path,
	
	tango.stdc.ctype,
	tango.stdc.stringz,
	tango.core.Array,
	
	tango.io.stream.Lines ,
	tango.io.device.Array ,
	tango.io.device.File ,
	tango.io.Console, 
	tango.io.Stdout ;
	
	extern(C){
		void exit(int);
	}

void main(char[][] args){
	G.Init(args) ;
	scope(exit){
		G.Exit ;
	}
	Class.Init ;
	Langz.Init ;
	Tag.Init ;
	version(DEV){
		Langz.Dump ;
		Class.Dump ;
		Tag.Dump ;
	}

	Stdout.formatln("convert `*.{}` to `*.{}` ", G.input_ext, G.output_ext ).flush ;
	int   conv_count	= 0 ;
	int	dir_count	= 0 ;
	auto fs	= new FileScan ;
	fs(".",  (FilePath fp, bool isDir){
		if( !isDir ){
			if( fp.ext  == G.input_ext ) {
				conv_count++ ;
				char[] input_file		= fp.toString ;
				char[] output_file	= fp.folder ~ fp.name ~ "." ~ G.output_ext ;
				if( Pth.exists(output_file) ) {
					auto input_time		= Pth.timeStamps( input_file ) ;
					auto output_time	= Pth.timeStamps( output_file ) ;
					if( output_time.modified >  input_time.modified && output_time.modified > G.Json_File_ts.modified ) {
						Stdout.formatln("  {}	--> 	{} ", input_file, output_file ).flush ;
						return false ;
					}
				}
				Stdout.formatln("  {}	==>	{} ", input_file, output_file ).flush ;
				G.Convert( input_file , output_file ) ;
			}
		}
		return isDir && count( fp.toString, '/' ) < 4 && ++dir_count < 999 ;
	});
	Stdout.formatln("file count: {}  ", conv_count ).flush ;
}

struct G {
	static char[]	Exe_Dir ;
	static char[]	Json_File ;
	static char[]	Tab_Empty ;
	static Jsonz*	jz ;
	static int		buf_size ;
	static bool		isExecOnConsole;
	
	static const char[] _ = "%" ;
	
	static tango.io.Path.FS.Stamps	Json_File_ts ;
	
	static char[] 
		Block_tag	= "div" ,
		Single_tag	= "hr" ,
		Inline_tag	= "span" ,
		Code_tag	= "ol" ,
		Line_tag	= "li" ,
		Empty_tag	= null ,
	
		input_ext	= "dml" ,
		output_ext	= "htm" ;

	
	static  void Init(char[][] args){
		if( args.length > 0 && args[0].length > 2 ){
			isExecOnConsole	= args[0][2] is '\\' ;
		}
		Tab_Empty.length		= 1024 ;
		foreach(ref c;Tab_Empty) c = '\t' ;
		scope wchar[250]	tmp;
		int	i	= GetModuleFileNameW(null, tmp.ptr, tmp.length);
		auto file	= Utf.toString(tmp[0..i]) ;
		scope fp	= new FilePath(file);
		Exe_Dir	= fp.parent.dup ;
		file		= fp.parent ~ "/" ~ fp.name ~ ".js" ;
		if( !Pth.exists(file) ){
			Stdout("configure file `")(file)("` is not  exists\n");
			G.Exit(1);
		}
		Json_File_ts= Pth.timeStamps(file);
		Json_File	= file.dup ;
		jz		= Jsonz.Load( Json_File );
		if( jz is null ){
			Stdout("`")(Json_File)("` is not a valid json file\n");
			G.Exit(1);
		}
		char[] v	= jz.Str("/input_ext") ;
		if( v !is null ){
			v	= Txt.trim(v);
			if( v.length ) input_ext	= v.dup ;
		}
		
		v	= jz.Str("/output_ext") ;
		if( v !is null ){
			v	= Txt.trim(v);
			if( v.length && v != input_ext ) output_ext = v.dup ;
		}
	}
	
	
	static  void Convert(char[] input_file, char[] output_file){
		if( !Pth.exists(input_file) ){
			Stdout("source file `")(input_file)("` is not exists\n");
			G.Exit(1);
		}
		auto da	= cast(char[]) File.get(input_file);
		buf_size	= da.length ;
		auto lz	= Linez.Load(da);
		
		Node* root		= Node.Nodes.allocate ;
		root.tag		= Tag.get(Tag.doc_tag, true );
		root.tag.type	= Tag.Type.Block ;
		root.parse(lz) ;
		
		Array bu 		= new Array(lz.size * 4 , lz.size * 16 );
		
		root.render(bu);
		File.set(output_file, bu.slice );
	}
	
	static void load_string(ref char[] value , char[] key, Json!(char).JsonObject* o){
		auto  _val	= o.value(key);
		if( _val is null ) return ;
		char[] val	= _val.toString ;
		if( val is null ) return ;
		value		= val.dup ;
	}
	
	static void Exit( int i = 0 ){
		Stdout.flush;
		if( isExecOnConsole ){
			Cin.get;
		}
		exit(i);
	}
}

struct Rule {
	char[]	key ;
	char[]	val ;
	static Allocator!(Rule) Rules ;
}

struct Class {
	char[]	name ;
	Rule*[]	rules ;
	static Allocator!(Class) Classes ;
	
	static Class*[]	list ;
	static Class* get(char[] name,  bool isCreate = false  ){
		Class* p = null ;
		foreach( _p ; list ){
			if( _p.name == name ){
				p	= _p ;
				break;
			}
		}
		if( !isCreate ){
			return p ;
		}
		if( p is null ){
			p		= Classes.allocate ;
			p.name	= name.dup ;
			list		~= p ;
		}
		return p ;
	}
	
	static  void Init(){
		auto p	= G.jz.GetValue("/class");
		if( p is null ) return ;
		auto o	= p.toObject ;
		Class* z ;
		
		if(o !is null) foreach(char[] k, Json!(char).Value v ; o.attributes){
			k	= Txt.trim(k);
			if( k.length is 0 ) continue ;
			if( v is null ) continue ;
			auto _o	= v.toObject ;
			
			z	= Class.get(k, true ) ;

			if(_o !is null) foreach(char[] _k, Json!(char).Value _v ; _o.attributes){
				_k	= Txt.trim(_k);
				if( _k.length is 0 ) continue ;
				if( _v is null ) continue ;
				char[] val	= Txt.trim(_v.toString) ;
				if( val.length is 0 ) continue ;
				Rule* r	= null ;
				foreach(_r; z.rules){
					if( _r.key	== _k ){
						r	= _r ;
						break;
					}
				}
				if( r is null ){
					r		= Rule.Rules.allocate ;
					z.rules	~= r ;
					r.key		= _k.dup ;
				}
				r.val	= val.dup ;
			}
			
		}
	}
	
	
	static void load(ref Class*[]	klass, char[] klass_key, Json!(char).JsonObject* o){
		auto  v	= o.value(klass_key);
		if( v is null ) return ;
		auto a	= v.toArray ;
		if( a is null ) return ;
		foreach( _v ; a) {
			if( _v is null ) continue ;
			char[] _val	=  _v.toString ;
			if( _val is null ) continue ;
			char[] val	= Txt.trim( _val ) ;
			if( val.length is 0 ) continue ;
			Class* p	= Class.get( val );
			if( p !is null ){
				klass	~= p ;
			}else{
				version(DEV){
					Stdout("-missing class `")(val)("` \n");
				}
			}
		}
	}
	
}


struct Tag {
	static const char[][] Types	= ["None", "Single", "Block", "Code" ] ;
	enum Type {
		None ,
		Single ,
		Block ,
		Code ,
	}
	static struct Decorator {
		char[]	
			tagName ,
			pre_html ,
			post_html ,
			style = null ;
		Class*[]
			klass ;
		
		static struct StyleNode {
			int	i ;
			int	j ;
		}
		
		void render_open(Array bu, char[] tagName , Class*[] _klass = null , char[] _style = null ){
			if( this.tagName !is null || tagName !is null ){
				bu("<")( this.tagName is null ? tagName : this.tagName );
				if( style !is null || klass.length > 0 ||  _klass !is null && _klass.length > 0 ){
					bu(` style="`);
					StyleNode[1024]	_heap ;
					int			_heap_i	= 0 ;
					
					bool add_Node(char[] key, char[] val) {
						assert( _heap_i < _heap.length ) ;
						char[] tmp	= cast(char[]) bu.slice ;
						foreach( int i , ref n ; _heap ){
							if( i >= _heap_i ){
								break ;
							}
							if( tmp[n.i .. n.j ] == key ){
								return false;
							}
						}
						StyleNode* n	= &_heap[_heap_i] ;
						n.i			= bu.slice.length;
						bu(key);
						n.j			= bu.slice.length  ;
						bu(":")(val)(";");
						_heap_i++ ;
						return false;
					}
					
					Each_Rule(_style, _klass, &add_Node);
					
					bu(`"`);
				}
				bu(">");
			}
			if( pre_html !is null ){
				bu(pre_html);
			}
		}
		
		void render_close(Array bu, char[] tagName ){
			if( post_html !is null ){
				bu(post_html);
			}
			if( this.tagName is null && tagName is null ){
				return ;
			}
			bu("</")( this.tagName is null ? tagName : this.tagName )(">");
		}
		
		void render(Array bu, char[] line, char[] _style = null  ){
			render_open(bu, G.Empty_tag, klass, _style);
			bu(line);
			render_close(bu, G.Empty_tag);
		}
		
		void load(Json!(char).JsonObject* o, char[] key = null ){
			Json!(char).JsonObject* _o	= o ;
			if( key !is null ){
				auto  v	= o.value(key);
				if( v is null ) return ;
				_o	= v.toObject ;
				if( _o is null ) return ;
			}
			G.load_string( tagName, "tagName", _o);
			G.load_string( style, "style", _o);
			G.load_string( pre_html, "pre_html", _o);
			G.load_string( post_html, "post_html", _o);
			Class.load(klass, "klass", _o);
		}
		
		alias bool delegate(char[] , char[] ) _DgRule ;
		bool Each_Rule(char[] _style, Class*[] _klass , _DgRule  dg){
			static bool add_style(char[] style, _DgRule  _dg){
				if( style is null ){
					return false ;
				}
				Textz tz ;
				tz.set(style);
				while( true ){
					char[] key	= tz.tok(':') ;
					if( key.length is 0 ) {
						break ;
					}
					char[] val	= tz.tok(';') ;
					if( val.length is 0 ) {
						break ;
					}
					if( _dg( Txt.trim(key) , Txt.trim(val) ) ) {
						return true ;
					}
				}
				return false ;
			}
			if( add_style(_style, dg) )	return true ;
			if( _klass !is null )  foreach( c; _klass)	foreach( r ; c.rules )	if( dg(r.key, r.val) ) return true ;
			if( add_style(style, dg) )	return true ;
			if( klass !is null )  foreach( c; klass)	foreach( r ; c.rules )	if( dg(r.key, r.val) ) return true ;
			return false ;
		}
	}
	Decorator	self, title, item ;
	Type		type ;
	char[]	name ;
	
	static char[]	doc_tag	= "doc" ;
	static Tag*[]	list ;
	static Allocator!(Tag)		Tags ;
	static Allocator!(Decorator)	Decorators ;
	
	static Tag* get(char[] name, bool isCreate = false  ){
		Tag* p = null ;
		foreach( _p ; list ){
			if( _p.name == name ){
				p	= _p ;
				break;
			}
		}
		if( !isCreate ){
			return p ;
		}
		if( p is null ){
			p		= Tags.allocate ;
			p.name	= name.dup ;
			list		~= p ;
		}
		return p ;
	}
	
	static  void Init(){
		auto p	= G.jz.GetValue("/tag");
		if( p is null ) return ;
		auto o	= p.toObject ;
		
		if(o !is null) foreach(char[] k, Json!(char).Value v ; o.attributes){
			k	= Txt.trim(k);
			if( k.length is 0 ) continue ;
			if( v is null ) continue ;
			auto _o	= v.toObject ;
			if( _o is null ) continue ;
			
			Tag*	tag	= get(k, true) ;
			char[] tp	= null ;
			
			tag.self.load(_o );
			tag.title.load(_o, "title" );
			tag.item.load(_o, "item" );
			
			G.load_string(tag.name, "name",  _o);
			G.load_string(tp, "type",  _o);
			
			if( tp is null ){
				tag.type	= Type.Block ;
			}else{
				tag.type	= cast(Type) find(Types, tp );
			}
		}
	}
	
}

struct Node {
	Tag*		tag ;
	Class*[]	klass	;
	Node*[]	childs ;
	Langz*	lang ;
	char[]	title, line , style = null ;
	Array		bu ;
	int		offset, line_number ;
	
	static Allocator!(Node) Nodes ;
	
	static bool isSpace(char[] line){
		foreach( c; line){
			if( ! Txt.isSpace(c) )
				return false ;
		}
		return true ;
	}
	
	static char[] get_tagName(char[] line){
		if( line.length < 2 ) return null ;
		if( line[0] != G._[0] ) return null ;
		int i = line.length ;
		foreach(int _i, c ; line){
			if( c is ' ' || c is '\n' || c is '\t' ){
				i	= _i ;
				break;
			}
		}
		return line[1..i];
	}
	
	bool isEnd(char[] line){
		foreach(int i,  c; line[0..offset] ){
			if( c !is '\t' ){
				return true;
			}
		}
		return false ;
	}
	
	void putLine(char[] line){
		if( tag.type is Tag.Type.Code ){
			bu(line)("\n");
		} else {
			if( line.length is 0 ){
				return ;
			}
			Node* it	= Nodes.allocate ;
			it.line		= line ;
			childs		~= it ;
		}
	}
	
	void parse(Linez* lz ){
		if( tag.type is Tag.Type.Single ){
			return ;
		}
		if(  tag.type is Tag.Type.Code  ){
			bu	= new Array( G.buf_size / 3 , G.buf_size );
		}
		line_number	= lz.line ;
	
		bool _isSpace ;
		Node* it		= Nodes.allocate ;

		for(char[] line = lz.get ; line !is null ; line = lz.get ){
			_isSpace	=  isSpace(line) ;
			if( !_isSpace && line.length < offset ){
				Stdout("tag ")(G._)( tag.name )(G._)(Int.toString(offset) )(" length ")(Int.toString(line.length) )(" on line ")(lz.line)(" \n");
				G.Exit(1) ;
			}
			if( !_isSpace && isEnd(line) ){
				lz.put(line) ;
				return ;
			}
			
			if( this.tag.type is Tag.Type.Code  ){
				if( _isSpace ){
					putLine("");
				}else{
					putLine(line[offset..$]);
				}
				continue ;
			}
			
			if( _isSpace ){
				continue ;
			}
			
			char[] tmp	=  get_tagName(line[offset..$]) ;
			if( tmp is null ){
				putLine(line[offset..$]);
				continue ;
			}
			
			memcpy(it, &Node.init, Node.sizeof );
			it.tag		= Tag.get( tmp );
			
			if( it.tag is null ){
				int i , len = tmp.length ;
				for( i = 0 ; i < len ; i ++ ) if( tmp[i] is '.' || tmp[i] is ':' ) break;
				it.tag		= Tag.get( tmp[0..i] );
				if( it.tag  is null ){
					Stdout("undefined tag `")(line[offset..$])("` on line ")(lz.line)(" \n");
					G.Exit(1) ;
				}
				if( i < len ){
					i++;
					int j ;
					Class* kz = null ;
					for( j = i  ; j < len; j++) if( tmp[j] is '.' ) break;
					if( tmp[i-1] is ':' ){
						it.lang	= Langz.get( tmp[i..j]  );
					}else{
						kz			= Class.get(  tmp[i..j] );
						if( kz	) it.klass	~= kz ;
					}
					if( j < len ){
						int pos = j++ ;
						for( int k = pos +1 ; k < len ; k++){
							if( tmp[k] is '.' ){
								kz			= Class.get(  tmp[ pos + 1 .. k ] );
								if( kz	) it.klass	~= kz ;
								pos	= k ;
							}
						}
						if( pos < len ){
							kz			= Class.get(  tmp[ pos + 1 .. $ ] );
							if( kz	) it.klass	~= kz ;
						}
					}
				}
			}
			
			it.offset	= offset + 1;
			
			int	pos	= offset + tmp.length + 1 ;
			if( pos < line.length ){
				char[] title		= Txt.trim(line[ pos .. $ ])  ;
				if( title.length !is 0 ){
					if(  title.length > 4 &&  title[0] is '(' && title[1] is '"' ){
						int i	= find(title, ')' ) ;
						if( i == title.length || i < title.length && Txt.isSpace( title[i+1]) ){
							if( title[i-1] is '"' ){
								it.style	= title[ 2 .. i - 1] ;
								if( i < title.length ){
									title	= title[ i + 1 .. $ ] ;
								}else{
									title	= null ;
								}
							}
						}
					}
					it.title	= title ; 
				}
			}
			
			it.parse(lz);
			childs		~= it ;
			it		= Nodes.allocate ;
		}
	}
	
	void render(Array bu){
		if( tag.type is Tag.Type.None ) {
			return ;
		}
	
		if( tag.self.Each_Rule(style, klass, (char[] key, char[] val){
			return key == "display" && val == "none" ;
		}) ) {
			return ;
		}

		if( tag.type is Tag.Type.Single ){
			tag.self.render_open(bu, G.Single_tag, klass );
			*( cast(ubyte*) &bu.slice[$-1])	= ' ' ;
			bu("/>");
			return ;
		}
		
		if( title !is null ){
			if( tag.title.tagName is null ){
				tag.self.render_open(bu, G.Block_tag , klass, style );
				if( tag.title.pre_html !is null ){
					bu(tag.title.pre_html);
				}
				bu(title);
				if( tag.title.post_html !is null ){
					bu(tag.title.post_html);
				}
			}else{
				tag.title.render(bu, title);
				bu("\n");
				tag.self.render_open(bu, G.Block_tag , klass, style);
			}
		}else{
			tag.self.render_open(bu, G.Block_tag , klass, style);
		}
		
		if( tag.type is Tag.Type.Code ){
			if( lang !is null){
				lang.render(bu , this) ;
			} else {
				bu( this.bu.slice );
			}
		}else{
			foreach( n ; childs ){
				if( n.tag is null ){
					bu("\n");
					tag.item.render(bu, n.line);
				}else{
					bu("\n");
					n.render(bu);
				}
			}
			if( childs.length > 0 ){
				bu("\n");
			}
		}
		tag.self.render_close(bu, G.Block_tag );
	}

}




struct Linez{
	private {
		Lines!(char)	ls ;
		int 			i ;
		char[][1024]	_ls ;
		int			_i ;	
		int			_size ;
	}
	
	static Linez* Load(char[] da){
		Linez* p	= new Linez;
		Array bu	= new Array(da.length);
		bu(da);
		p.ls		= new Lines!(char)(bu);
		p._size	= da.length ;
		return p;
	}
	
	void put(char[] line){
		if( _i >= _ls.length ){
			Stdout("Linez put error \n");
			G.Exit(1);
		}
		_ls[_i]	= line ;
		_i++;
	}
	
	char[] get(){
		if( _i > 0 ){
			_i-- ;
			return _ls[_i] ;
		}
		i++;
		return ls.next ;
	}
	
	int line() {
		return i  - _i ;
	}
	
	int size(){
		return _size ;
	}
}
struct Textz {
	
	char[] 	buf ;
	int		pos , len  ;

	void set(char[] str){
		buf	= str ;
		pos	= 0 ;
		len	= str.length ;
	}

	char[] tok(char _tok ){
		if( pos >= len ){
			return null ;
		}
		int _pos = pos ;
		for( int i = pos ; i < len; i++){
			if( buf[i] is _tok ){
				pos	= i+1 ;
				return buf[ _pos .. i ] ;
			}
		}
		pos	= len ;
		return buf[ _pos .. $ ] ;
	}
	
	char[] rtok(char _tok ){
		if( len <= pos ){
			return null ;
		}
		int _len	= len ;
		for( int i = len -1 ;  i > pos ; i-- ){
			if( buf[i] is _tok ){
				len	= i  ;
				return buf[ i +1 .. _len ] ;
				break;
			}
		}
		len	= pos ;
		return buf[ pos .. _len ] ;
	}
}

struct Jsonz {
	alias Json!(char).Type		JType ;
	alias Json!(char).JsonValue		JValue ;
	alias Json!(char).NameValue		JPair ;
	alias Json!(char).JsonObject	JObject ;

	private {
		Json!(char)
				json		= null ;
		JValue*	root  	= null ;
		JValue*	now  	= null ;
	}
	
	static Jsonz* Load(char[] file){
		char[] da	= cast(char[]) File.get(file);
		Jsonz* p	= new Jsonz ;
		p.json	= new Json!(char) ;
		try{
			p.root	= p.json.parse ( da ) ;
		}catch(Exception e) {
			return null ;
		}
		return p ;
	}
	
	void Save(){
		scope bu	= new Array(1024, 1024) ;
		root.print((char[] c) {bu(c);	}, "\t");
		Stdout.formatln("{}",  cast(char[]) bu.slice );
	}
	
	JValue* GetValue(char[] path){
		Textz t ;
		t.set( path ) ;
		return GetValue(&t);
	}
	
	private JValue* GetValue(Textz* t){
		if( root is null ){
			return null ;
		}
		JValue* node ;
		if( t.buf[0] is '/' ){
			t.tok('/');
			node	= root ;
		}else{
			node	= now is null ? root : now ;
		}
		JObject*	obj ;
		char[]	_k  	= null ;
		for( char[] k =  t.tok('/') ; k !is null; k =  t.tok('/') ){
			if( _k !is null ){
				obj	= node.toObject ;
				if( obj is null ){
					return null ;
				}
				node	= obj.value ( _k );
				if( node is null ){
					return null ;
				}
			}
			_k	= k ;
		}
		if( _k  !is null ){
			obj	= node.toObject ;
			if( obj is null ){
				return null ;
			}
			return obj.value ( _k );
		}
		return null ;
	}
	
	private JValue* CreateValue(char[] path){
		if( root is null ){
			root	=  json.object ;
		}
		
		char[]	_k  	= null ;
		JValue*	node	= root ;
		JObject*	obj	= null ;
		
		Textz t ;
		t.set(path);
		
		if( t.buf[0] is '/' ){
			node	= root ;
		}else{
			node	= now is null ? root : now ;
		}
		
		for( char[] k =  t.tok('/') ; k !is null; k =  t.tok('/') ){
			if( _k !is null  && _k != "" ){
				obj	= node.toObject ;
				if( obj is null ){
					return _CreateValue(&t, k, node) ;
				}
				node	= obj.value ( _k );
				if( node is null ){
					JValue* valz	= json.value(true) ;
					obj.append( json.pair( _k, valz) );
					return _CreateValue(&t, k, valz) ;
				}
			}
			_k	= k ;
		}
		
		if( obj !is null ){
			JValue* valz	= json.value(true) ;
			obj.append( json.pair(_k, valz ) );
			return valz ;
		}
		return null ;
	}
	
	private JValue* _CreateValue(Textz* t, char[] _key, JValue* _root ){
		JValue* 	v	= json.value( true ) ;
		JValue* 	_v 	= v ;
		for( char[] k = t.rtok('/') ; k !is null; k = t.rtok('/') ){
			if( k != "" ){
				v	= json.object(	json.pair( k, v )) ;
			}
		}
		_SetValue(_root, json.object(
				json.pair( _key, v )
			));
		return _v ;
	}
	
	Jsonz* setPath(char[] path){
		now		= GetValue(path) ;
		return this;
	}
	
	Jsonz* Set(T)( char[] path ,  T val  ){
		Textz  t;
		t.set(path) ;
		JValue* v	= GetValue( &t ) ;
		if( v is null ){
			v	= CreateValue( path ) ;
		}
		static if( is( T == bool) || isRealType!(T) || isIntegerType!(T) ||  is(ElementTypeOfArray!(T)==char)  ){
			v.set( val );
		}else static if( isPointerType!(T)  ){
			static if(  is( T == JValue* ) ){
				_SetValue(v, val );
			}else static if( is ( T == Jsonz*  ) ){
				_SetValue(v, val.root );
			}
		}else static if( isAssocArrayType!(T) ){
			JValue* valz		= json.object ;
			static assert(false, T.stringof );
		}else static if( isArrayType!(T) ){
			JValue[] Val	= new JValue[ val.length ];
			JValue*[] Valz	= new JValue*[ val.length ];
			foreach(int i, e ; val ){
				Valz[i]	= Val[i].set( e );
			}
			v.set( Valz );
		}else{
			static assert(false, T.stringof );
		}
		return this ;
	}
	
	static private void _SetValue(JValue*  o, JValue*  n){
		if( n is null ){
			o.reset;
		}else 
			switch(n.type){
				case JType.String :
				case JType.RawString :
						o.set( n.toString );
					break ;
				case JType.Number :
						o.set( n.toNumber );
					break;
				case JType.True :
				case JType.False :
						o.set( n.toBool );
					break;
				case JType.Object:
						o.set( n.toObject );
					break;
				case JType.	Array:
						o.set( n.toArray );
					break;
			}
	}
	
	char[] Str(char[] path){
		JValue* v = GetValue( path ) ;
		if( v ){
			return v.toString ;
		}
		return null ;
	}
	
	int Int(char[] path){
		JValue* v = GetValue( path ) ;
		if( v ){
			return cast(int) v.toNumber ;
		}
		return 0 ;
	}
}


struct Tok {
	Type		type ;
	char[]	tok ;
	int		line, line_end ;
	
	static const char[][] Types = [
		"Macro" ,
		"Identifier",
		"Comment" ,
		"Notation" ,
		"Space" ,
		"Number" ,
		"String" ,
		"Operater" ,
	] ;
	
	static const char[][] Tags = [
		null , 
		null ,
		"span" ,
		"span" ,
		null ,
		"span" , 
		"span" ,
		null ,
	] ;
	
	enum Type {
		Macro , 
		Identifier ,
		Comment ,
		Notation ,
		Space ,
		Number ,
		String ,
		Operater ,
	}

	static struct Decorator {
		int				type, open_i, pos_i , close_i ;
		Decorator*[char[]]	keys ;
		
		static Allocator!(Decorator) Decorators ;
		private static Array 	bu ;
		private static ubyte*	start_pos ;
		
		ubyte[] open(){
			return start_pos[ open_i .. pos_i ];
		}
		
		ubyte[] close(){
			return start_pos[ pos_i .. close_i ];
		}
		
		static Decorator* gen(Tag.Decorator* node, int type){
			if( bu is null ){
				bu		= new Array(16, 32 ) ;
				bu("\0");
			}
			
			Decorator* p	= Decorators.allocate ;
			p.type		= type ;
			if( node !is null ){
				p.open_i	= bu.limit ;
				node.render_open(bu, Tok.Tags[ type ] );
				p.pos_i	= bu.limit ;
			
				node.render_close(bu, Tok.Tags[ type ] );
				p.close_i	= bu.limit ;
				
				start_pos	= cast(ubyte*) &bu.slice[0] ;
			}
			return p;
		}
		
		private static void Reset(){
			size_t*	ptr	= cast(size_t*) &Decorators;
			Decorator*	list	= cast(Decorator*) ptr;
			Decorator[]* lists	= cast(Decorator[]*) (ptr+2);
			size_t	len	= ptr[4];
			size_t	lens	= ptr[5];
			
			for(int i = 0 ; i < len; i++){
				Decorator*	_ptr	= &list[i] ;
				
			}
			for(int i = 0 ; i < lens; i++){
				for(int j = 0 ; j < lists[i].length; i++ ){
					Decorator*	_ptr	= &lists[i][j];
					
				}
			}
		}
	}
}

struct Lexer {
	static ubyte[][] Operater_4	= cast(ubyte[][]) [ ">>>=", "!<>=" ] ;
	static ubyte[][] Operater_3	= cast(ubyte[][]) [ ">>>", ">>=", "<<=", "<>=",  "!<>", "!>=", "!<=" , "..." ] ;
	static ubyte[][] Operater_2	= cast(ubyte[][]) [ 
			"==", ">=", "<=" , ">>", "<<", "[]" , ".." , "!<", "!>", "!=" ,
			"||", "|=", "&&", "&=" , "++", "+=", "--", "-=", "~=", "*=", "^=", "%="
		] ;
	
	ubyte[]	bu ;
	ubyte*	now , start, end ;
	int		index , line_i , line_o ;
	
	static Lexer* Load(Node* node ){
		Lexer* lx 	= new Lexer ;
		lx.bu		= cast(ubyte[]) node.bu.slice ;
		if( lx.bu.length > 1 ){
			lx.now	= &lx.bu[0];
			lx.start	= lx.now ;
			for( lx.end = &lx.bu[$-1]; lx.end > lx.now; lx.end-- ){
				if( lx.end[0] is ' ' || lx.end[0] is '\t' || lx.end[0] is '\n' || lx.end[0] is '\r' ){
					continue ;
				}
				break ;
			}
		}
		lx.line_o	= node.line_number ;
		return lx ;
	}
	
	Tok* next(Tok* tok){
		while( now > end ){
			return null ;
		}
		ubyte* _now	= now ;
		tok.line		= line_i ;
		tok.type		= cast(Tok.Type) -1 ;
		
		if( _now[0] is '#' ){
			tok.type	= Tok.Type.Macro ;
			_now++;
			while ( (_now <= end) && _now[0] !is '\n'  && _now[0] !is '\r' ) {
				if( _now[0] is '\\' ){
					_now++;
					if( _now[0] is '\n' )
						line_i++ ;
				}
				_now ++;
			}
		}else if( _now[0] is '/' && _now < end && ( _now[1] is '/' || _now[1] is '+' || _now[1] is '*'  ) ){ // Check for comments ...
			tok.type	= Tok.Type.Comment ;
			if ( _now[1] is '*') { // Check for multiline unnested comments
				_now	+=	2 ;
				while ( _now < end) {
					if ( _now[0] == '*' && _now [1] == '/') {
						_now	+=	2 ;
						break;
					}else{
						if( _now[0] is '\n' ) line_i++ ;
						_now++;
					}
				}
			}else if ( _now[1] == '/') {  // Check for single line comments
				_now += 2;
				while ( (_now <= end ) && ( _now[0] != '\n')) {
					_now++;
				}
			}else if ( _now[1] == '+') {  // Check for multiline nested comments
				int lDepth = 1;
				_now += 2;
				while ( _now <= end ) {
					if ( _now[0] == '/' ){
						if ( ( _now < end) && (_now [1] == '+') ) {
							lDepth++;
							_now += 2;
						} else{
							if( _now[0] is '\n' ) line_i++ ;
							_now++ ;
						}
					} else if ( _now[0] == '+') {
						if ( ( _now < end ) && ( _now[1] == '/')) {
							_now += 2;
							if (lDepth == 1) {
							    break;
							}
							lDepth--;
						} else{
							if( _now[0] is '\n' ) line_i++ ;
							_now++;
						}
					} else {
						_now++;
					}
				}
				if ( _now > end ) {
					Stdout("Mismatched nested comments line: [")
							( Int.toString( line_o) )
								(", ")
							( Int.toString(tok.line + line_o ) )
								(", ")
							( Int.toString(line_i + line_o ) )
						("]`\n" ); 
					G.Exit(1);
				}
			}
		}else if( _now[0] is '\'' || _now[0] is '"' || _now[0] is '`' || ( _now < end - 2 &&  _now[0] is 'r' && _now[1] is '"' ) ){  // Check for 'words'
			tok.type	= Tok.Type.String ;
			char quote	= _now[0] ;
			if( quote is 'r' ){
				_now++ ;
				quote 	= '"' ;
			}
			_now++;
			while ( _now <= end ){
				if( _now[0] is quote){
					_now++;
					break ;
				}
				if( _now[0] is '\\' ){
					_now++;
				}
				if( _now[0] is '\n' ) line_i++ ;
				_now++;
			}
			if( _now <= end && (_now[0] is 'c' || _now[0] is 'w' || _now[0] is 'd') ){
				_now++;
			}
		}else if( (_now[0] >= 'a' &&  _now[0] <= 'z') || (_now[0] >= 'A' &&  _now[0] <= 'Z') || _now[0] is '_'  ){  // Check for Identifier
			tok.line	= line_i ;
			_now++;
			while ( (_now <= end) && ( ( isalnum ( _now[0]) || _now[0] == '_') )) {
				_now ++;
			}
			tok.type	= Tok.Type.Identifier ;
		} else if ( _now[0] is ' ' ||  _now[0] is '\t' ||  _now[0] is '\r'  ||  _now[0] is '\n' ) {   // Check for whitespace
			tok.type	= Tok.Type.Space ;
			while ( (_now <= end) && ( _now[0] is ' ' ||  _now[0] is '\t' ||  _now[0] is '\r'  ||  _now[0] is '\n'  ) ) {
				if( _now[0] is '\n' ) line_i++ ;
				_now ++;
			}
		} else if ( _now[0] >='0' && _now[0] <= '9'  ) {   // Check for number
			tok.type	= Tok.Type.Number ;
			
			bool	isDot		= false ;
			if( _now[0] is '0' ){
				_now++ ;
				if( _now[0] is 'x' || _now[0] is 'X' ){
					_now++ ;
					while ( (_now <= end) && (  (  _now[0] >= '0' &&  _now[0] <= '9' ) || (  _now[0] >= 'a' &&  _now[0] <= 'f' ) || (  _now[0] >= 'A' &&  _now[0] <= 'F' )  || _now[0] == '_' ) ) {
						_now ++;
					}
				}else if( _now[0] is 'b' || _now[0] is 'B' ){
					_now++ ;
					while ( (_now <= end) && ( _now[0] == '0'  || _now[0] == '1'   || _now[0] == '_' ) ) {
						_now ++;
					}
				} else {
					while ( (_now <= end) && ( ( _now[0] >= '0' && _now[0] < '8' )  || _now[0] == '_' ) ) {
						_now ++;
						if( !isDot && _now[0] is '.' && ( _now < end && _now[1] >= '0' &&_now[1] < '8'  ) ){
							_now ++;
							isDot	= true ;
						}
					}
				}
			}else{
				while ( (_now <= end) && ( ( _now[0] >= '0' && _now[0] <= '9' )  || _now[0] == '_' ) ) {
					_now ++;
					if( !isDot && _now[0] is '.' && ( _now < end && _now[1] >= '0' &&_now[1] <= '9'  ) ){
						_now ++;
						isDot	= true ;
					}
				}
			}
			if( _now[0] is 'F' || _now[0] is 'L' ){
				_now ++;
			}
			if( isalpha(_now[0]) || ( _now[0] >= '0' && _now[0] <= '9' ) ||  _now[0] is '_'   ){
				Stdout("Mismatched number line:`")(Int.toString(tok.line))("` value is  ")(cast(char[]) now[0.._now-now])("\n" ); 
				G.Exit(1);
			}
		}else if ( _now[0] is G._[0] ){
			tok.type	= Tok.Type.Notation ;
			_now++;
			while ( (_now <= end) && ( ( isalnum ( _now[0]) || _now[0] == '_') )) {
				_now ++;
			}
		}else{  // Return whatever character we have discovered.
			tok.type	= Tok.Type.Operater ;
			
			if( _now + 4 < end && contains(Operater_4 , _now[0..4]) ){
				_now += 4 ; 
			}else if( _now + 3 < end && contains(Operater_3 , _now[0..3]) ){
				_now += 3 ; 
			}else if( _now + 2 < end && contains(Operater_2 , _now[0..2]) ){
				_now += 2 ; 
			}else{
				_now++;
			}
			
		}
		if( _now is now ){
			Stdout("null tok [")
				( Int.toString( line_o) )
					(", ")
				( Int.toString(tok.line + line_o ) )
					(", ")
				( Int.toString(line_i + line_o ) )
			("] Tok.Type is ")( Int.toString(tok.type) )
				(" val[ -2 .. 2 ] `")( cast(char[])(now-2)[0..4] )
			("` \n")  ;
			G.Exit(1);
		}
		tok.tok		= cast(char[]) now[0.._now-now]  ;
		tok.line_end	= line_i ;
		now			= _now ;
		return tok;
	}
	
}



struct Langz {
	char[]	name ,
			use ,
			tag ,
			style ,
			li_style = null ;
	
	Tag.Decorator
			self, item , token ;
	
	Tok.Decorator*[Tok.Types.length]
			tokz ;
	
	Langz*	parent ;
	
	
	static Langz*[] list ;
	static void Init(){
		auto val	= G.jz.GetValue("/language");
		if( val is null ) return ;
		auto obj	= val.toObject ;
		if( obj !is null ) foreach(char[] lang, Json!(char).Value _val ; obj.attributes){
			if( _val is null ) continue ;
			auto _obj		= _val.toObject ;
			if( _obj is null ) continue ;
			Langz* langz	= get(lang, true) ;
			
			G.load_string(langz.use, "use", _obj );
			langz.self.load( _obj );
			langz.item.load( _obj , "line" );

			foreach(char[] type, Json!(char).Value tokz ; _obj.attributes ) {
				if( tokz is null  || type  is null ) continue ;
				int ti	= find( Tok.Types,  type ) ;
				if( ti is Tok.Types.length ) {
					continue ;
				}
				auto _tokz		= tokz.toObject ;
				if( _tokz is null ) 
					continue ;
				auto tn		= Tag.Decorators.allocate ;
				tn.load(_tokz ) ;
				langz.tokz[ti]	= Tok.Decorator.gen(tn , ti);
				
				auto kKeys		= _tokz.value("keys");
				if( kKeys is null ) 
					continue ;
				auto oKeys		= kKeys.toObject ;
				if( oKeys is null ) 
					continue ;
				foreach( char[] _keys, Json!(char).Value vNode ; oKeys.attributes ){
					if( _keys is null )
						continue ;
					if( vNode is null )
						continue ;
					auto oNode	= vNode.toObject ;
					if( oNode is null )
						continue ;
					auto	_aKeys	= Txt.split( Txt.trim(_keys) , " ");
					if( _aKeys.length is 0 ) 
							continue;
					
					auto tn		= Tag.Decorators.allocate ;
					tn.load( oNode ) ;
					Tok.Decorator* tz = Tok.Decorator.gen(tn, ti) ;
					
					foreach( _key ;  _aKeys){
						_key	= Txt.trim(_key);
						if ( _key.length is 0 ) continue ;
						langz.tokz[ti].keys[_key.dup]
							= tz ;
					}
				}

			}
			
			if( langz.use !is null )
				langz.parent	= get(langz.use) ;
			
			foreach(int type, ref tz; langz.tokz )
				if( tz is null ){
					if( langz.parent is null ){
						tz	= Tok.Decorator.gen(null, type);
					}else{
						tz	= langz.parent.tokz[type] ;
					}
				}
		}
	}
	
	void Dump(){
		foreach(int type, ref tz; tokz ){
			Stdout.formatln("{}: `{}`, `{}`", name, Tok.Types[type], cast(char[]) tz.open, cast(char[]) tz.close);
			if( tz.keys.length > 0 ) foreach(char[] key, tz; tz.keys){
				Stdout.formatln("\t `{}` = `{}` `{}`", key, cast(char[]) tz.open, cast(char[]) tz.close);
			}
		}
	}
	
	static Langz* get(char[] name, bool isCreate = false ){
		foreach( _lang ; list ){
			if ( _lang.name == name ){
				return _lang ;
			}
		}
		if( !isCreate ){
			return null ;
		}
		Langz* lang= new Langz ;
		lang.name	= name.dup ;
		list		~=	lang ;
		return lang ;
	}

		
	void render(Array bu, Node* node){
		//Dump;
		
		if( self.tagName is null && parent !is null ){
			parent.self.render_open(bu, G.Code_tag );
		}else{
			self.render_open(bu, G.Code_tag );
		}
		
		ubyte[1024] 	_tmp1, _tmp2 ;
		scope _li_open	= new Array(_tmp1);
		scope _li_close	= new Array(_tmp2);
		_li_open.clear ;
		_li_close.clear ;
		item.render_open(_li_open, G.Line_tag );
		item.render_close(_li_close, G.Line_tag );
					
		bu(_li_open.slice);
		
		Tok	tok ;
		Tok.Decorator*	tz ;
		Tok.Decorator**	tzz ;
		
		auto lexer		= Lexer.Load( node );
		while( lexer.next(&tok) !is null ){
			if( tok.type < tokz.length ){
				tz	= tokz[tok.type] ;
				if( tz.keys.length ){
					tzz	= tok.tok in tz.keys ;
					if( tzz !is null )
						tz	= *tzz ;
				}
				bu( tz.open );
				char* _end  = &tok.tok[$-1];
				for( char* _ptr = &tok.tok[0]; _ptr <= _end; _ptr++){
					switch(_ptr[0]){
						case ' ':
							bu("&nbsp;");
							break;
						case '&':
							bu("&amp;");
							break;
						case '\t':
							bu("&nbsp;&nbsp;&nbsp;&nbsp;");
							break;
						case '\r':
							break;
						case '\n':
							bu( tz.close )( _li_close.slice )
									("\n")
								( _li_open.slice)(   tz.open );
							break;
						case '<':
							bu("&lt;");
							break;
						case '>':
							bu("&gt;");
							break;
						default:
							bu(_ptr[0..1]);
					}
				}
				bu(tz.close);
			}
		}
		
		bu(_li_close.slice)("\n");
		if( self.tagName is null && parent !is null ){
			parent.self.render_close(bu, G.Code_tag );
		}else{
			self.render_close(bu, G.Code_tag );
		}
	}
}

struct Allocator(T){
	private T[]	list;
	private T[][]	lists;
	private int		index,
				block;
	void reset () {
		block = -1;
		newlist;
	}

	T* allocate () {
		if (index >= list.length)
		    newlist;
		auto p = &list [index++];
		return p;
	}

	private void newlist () {
		index = 0;
		if ( block >= lists.length ){
			lists	~= new T[128];
		}
		list = lists [block++];
	}
}